﻿using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace LS.DicomViewer.Common
{
    public class WpfImageManager : IImageManager
    {
        IImage IImageManager.CreateImage(int width, int height)
        {
            return new WpfImage(width, height);
        }

        public static void Init()
        {
            new DicomSetupBuilder().RegisterServices(s =>
            s.AddFellowOakDicom()
            .AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
            .AddImageManager<WpfImageManager>())
                .SkipValidation()
                .Build();
        }
    }

    internal sealed class WpfImage : ImageBase<WriteableBitmap>
    {
        #region FIELDS
        private const double Dpi = 96;
        #endregion

        #region CONSTRUCTORS

        public WpfImage(int width, int height) : base(width, height, new PinnedIntArray(width * height), null)
        {
        }

        private WpfImage(int width, int height, PinnedIntArray pixels, WriteableBitmap image) : base(width, height, pixels, image)
        {
        }
        #endregion

        public override T As<T>()
        {
            if (image == null)
            {
                throw new DicomImagingException("Image has not yet been rendered.");
            }
            if (!typeof(T).GetTypeInfo().IsAssignableFrom(typeof(WriteableBitmap).GetTypeInfo()))
            {
                throw new DicomImagingException(string.Format("Cannot cast to '{0}'; type must be assignable from '{1}'", typeof(T).Name, typeof(WriteableBitmap).Name));
            }
            var img = (T)(object)image;
            return img;
        }

        #region METHODS

        public override void Render(int components, bool flipX, bool flipY, int rotation)
        {
            var bitmap = CreateBitMap(width, height, components, pixels.Data);
            image = ApplyFlipRotate(bitmap, flipX, flipY, rotation);
        }

        public override void DrawGraphics(IEnumerable<IGraphic> graphics)
        {
            graphics.AsParallel().ForAll(graphic =>
            {
                var layer = graphic.RenderImage(null).As<WriteableBitmap>();

                var overlay = new int[graphic.ScaledWidth * graphic.ScaledHeight];
                var stride = 4 * graphic.ScaledWidth;
                layer.CopyPixels(overlay, stride, 0);

                image.WritePixels(
                    new Int32Rect(
                        graphic.ScaledOffsetX,
                        graphic.ScaledOffsetY,
                        graphic.ScaledWidth,
                        graphic.ScaledHeight),
                    overlay,
                    stride,
                    0);
            });
        }

        public override FellowOakDicom.Imaging.IImage Clone()
        {
            return new WpfImage(
                width,
                height,
                new PinnedIntArray(pixels.Data),
                image == null ? null : new WriteableBitmap(image));
        }

        private static WriteableBitmap CreateBitMap(int width, int height, int components, int[] pixelData)
        {
            var format = components == 4 ? PixelFormats.Bgra32 : PixelFormats.Bgr32;
            var bitmap = new WriteableBitmap(width, height, Dpi, Dpi, format, null);
            bitmap.Lock();
            Marshal.Copy(pixelData, 0, bitmap.BackBuffer, pixelData.Length);
            bitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
            bitmap.Unlock();
            return bitmap;
        }

        private static WriteableBitmap ApplyFlipRotate(WriteableBitmap bitmap, bool flipX, bool flipY, int rotation)
        {
            if (rotation == 0 && !flipX && !flipY)
            {
                return bitmap;
            }

            var rotFlipTransform = new TransformGroup();
            rotFlipTransform.Children.Add(new RotateTransform(rotation));
            rotFlipTransform.Children.Add(new ScaleTransform(flipX ? -1 : 1, flipY ? -1 : 1));

            return new WriteableBitmap(new TransformedBitmap(bitmap, rotFlipTransform));
        }
        #endregion
    }
}
